/**
 * Copyright (c) 2005-2012 https://github.com/zhangkaitao
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package com.tyf.common.dao;

import com.tyf.common.domain.search.Searchable;
import com.tyf.common.dao.callback.SearchCallback;
import com.tyf.common.dao.hibernate.HibernateUtils;
import com.tyf.common.dao.support.annotation.EnableQueryCache;
import com.tyf.common.util.Collections3;
import com.tyf.common.util.CommonUtils;
import com.tyf.common.util.ThreadLocals;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.transform.Transformers;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.domain.*;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.orm.jpa.SharedEntityManagerCreator;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import java.util.List;
import java.util.Map;

/**
 * 仓库辅助类
 * <p>User: Zhang Kaitao
 * <p>Date: 13-4-14 下午5:28
 * <p>Version: 1.0
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class DaoHelper {

    private static EntityManager entityManager;
    private Class<?> entityClass;
    private boolean enableQueryCache = false;
    
    /**
     * @lt add
     */
    private SearchCallback searchCallback = SearchCallback.DEFAULT;
    private static final String paramPrefix = "param_";
    
    /**
     * @param entityClass 是否开启查询缓存
     */
    public DaoHelper(Class<?> entityClass) {
        this.entityClass = entityClass;

        EnableQueryCache enableQueryCacheAnnotation =
                AnnotationUtils.findAnnotation(entityClass, EnableQueryCache.class);

        boolean enableQueryCache = false;
        if (enableQueryCacheAnnotation != null) {
            enableQueryCache = enableQueryCacheAnnotation.value();
        }
        this.enableQueryCache = enableQueryCache;
    }

    public static void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory);
    }

    public static EntityManager getEntityManager() {
        Assert.notNull(entityManager, "entityManager must not null, please see " +
                "[com.tyf.common.repository.RepositoryHelper#setEntityManagerFactory]");

        return entityManager;
    }


    public static void flush() {
        getEntityManager().flush();
    }

    public static void clear() {
        flush();
        getEntityManager().clear();
    }

    /**
     * <p>ql条件查询<br/>
     * searchCallback默认实现请参考 {@see com.tyf.common.repository.callback.DefaultSearchCallback}<br/>
     * <p/>
     * 测试用例请参考：{@see com.tyf.common.repository.UserRepositoryImplForCustomSearchIT}
     * 和{@see com.tyf.common.repository.UserRepositoryImplForDefaultSearchIT}
     *
     * @param ql
     * @param searchable     查询条件、分页 排序
     * @param searchCallback 查询回调  自定义设置查询条件和赋值
     * @return
     */
    public <M> List<M> findAll(final String ql, final Searchable searchable, final SearchCallback searchCallback) {

        assertConverted(searchable);
        StringBuilder s = new StringBuilder(ql);
        searchCallback.prepareQL(s, searchable);
        searchCallback.prepareOrder(s, searchable);
        Query query = getEntityManager().createQuery(s.toString());
        applyEnableQueryCache(query);
        searchCallback.setValues(query, searchable);
        searchCallback.setPageable(query, searchable);

        return query.getResultList();
    }

    /**
     * <p>按条件统计<br/>
     * 测试用例请参考：{@see com.tyf.common.repository.UserRepositoryImplForCustomSearchIT}
     * 和{@see com.tyf.common.repository.UserRepositoryImplForDefaultSearchIT}
     *
     * @param ql
     * @param searchable
     * @param searchCallback
     * @return
     */
    public long count(final String ql, final Searchable searchable, final SearchCallback searchCallback) {

        assertConverted(searchable);

        StringBuilder s = new StringBuilder(ql);
        searchCallback.prepareQL(s, searchable);
        Query query = getEntityManager().createQuery(s.toString());
        applyEnableQueryCache(query);
        searchCallback.setValues(query, searchable);

        return (Long) query.getSingleResult();
    }

    /**
     * 按条件查询一个实体
     *
     * @param ql
     * @param searchable
     * @param searchCallback
     * @return
     */
    public <M> M findOne(final String ql, final Searchable searchable, final SearchCallback searchCallback) {

        assertConverted(searchable);

        StringBuilder s = new StringBuilder(ql);
        searchCallback.prepareQL(s, searchable);
        searchCallback.prepareOrder(s, searchable);
        Query query = getEntityManager().createQuery(s.toString());
        applyEnableQueryCache(query);
        searchCallback.setValues(query, searchable);
        searchCallback.setPageable(query, searchable);
        query.setMaxResults(1);
        List<M> result = query.getResultList();

        if (result.size() > 0) {
            return result.get(0);
        }
        return null;
    }


    /**
     * @param ql
     * @param params
     * @param <M>
     * @return
     * @see DaoHelper#findAll(String, org.springframework.data.domain.Pageable, Object...)
     */
    public <M> List<M> findAll(final String ql, final Object... params) {

        //此处必须 (Pageable) null  否则默认有调用自己了 可变参列表
        return findAll(ql, (Pageable) null, params);

    }

    /**
     * <p>根据ql和按照索引顺序的params执行ql，pageable存储分页信息 null表示不分页<br/>
     * 具体使用请参考测试用例：{@see com.tyf.common.repository.UserRepository2ImplIT#testFindAll()}
     *
     * @param ql
     * @param pageable null表示不分页
     * @param params
     * @param <M>
     * @return
     */
    public <M> List<M> findAll(final String ql, final Pageable pageable, final Object... params) {

        Query query = getEntityManager().createQuery(ql + prepareOrder(pageable != null ? pageable.getSort() : null));
        applyEnableQueryCache(query);
        setParameters(query, params);
        if (pageable != null) {
            query.setFirstResult(pageable.getOffset());
            query.setMaxResults(pageable.getPageSize());
        }

        return query.getResultList();
    }

    /**
     * <p>根据ql和按照索引顺序的params执行ql，sort存储排序信息 null表示不排序<br/>
     * 具体使用请参考测试用例：{@see com.tyf.common.repository.UserRepository2ImplIT#testFindAll()}
     *
     * @param ql
     * @param sort   null表示不排序
     * @param params
     * @param <M>
     * @return
     */
    public <M> List<M> findAll(final String ql, final Sort sort, final Object... params) {

        Query query = getEntityManager().createQuery(ql + prepareOrder(sort));
        applyEnableQueryCache(query);
        setParameters(query, params);

        return query.getResultList();
    }


    /**
     * <p>根据ql和按照索引顺序的params查询一个实体<br/>
     * 具体使用请参考测试用例：{@see com.tyf.common.repository.UserRepository2ImplIT#testFindOne()}
     *
     * @param ql
     * @param params
     * @param <M>
     * @return
     */
    public <M> M findOne(final String ql, final Object... params) {

        List<M> list = findAll(ql, new PageRequest(0, 1), params);

        if (list.size() > 0) {
            return list.get(0);
        }
        return null;
    }


    /**
     * <p>根据ql和按照索引顺序的params执行ql统计<br/>
     * 具体使用请参考测试用例：com.tyf.common.repository.UserRepository2ImplIT#testCountAll()
     *
     * @param ql
     * @param params
     * @return
     */
    public long count(final String ql, final Object... params) {

        Query query = entityManager.createQuery(ql);
        applyEnableQueryCache(query);
        setParameters(query, params);

        return (Long) query.getSingleResult();
    }

    /**
     * <p>执行批处理语句.如 之间insert, update, delete 等.<br/>
     * 具体使用请参考测试用例：{@see com.tyf.common.repository.UserRepository2ImplIT#testBatchUpdate()}
     *
     * @param ql
     * @param params
     * @return
     */
    public int batchUpdate(final String ql, final Object... params) {
        // 执行批处理的update语句前，先clear实体管理器，避免因当前实体存在数据变更，而默认执行了entityManager的flush
        // 不能使用clear()，可能一些正常的未flush数据也被清空，丢失更新
//        getEntityManager().clear();

        Query query = getEntityManager().createQuery(ql);
        setParameters(query, params);

        return query.executeUpdate();
    }


    /**
     * 按顺序设置Query参数
     *
     * @param query
     * @param params
     */
    public void setParameters(Query query, Object[] params) {
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                query.setParameter(i + 1, params[i]);
            }
        }
    }

    /**
     * 拼排序
     *
     * @param sort
     * @return
     */
    public String prepareOrder(Sort sort) {
        if (sort == null || !sort.iterator().hasNext()) {
            return "";
        }
        StringBuilder orderBy = new StringBuilder("");
        orderBy.append(" order by ");
        orderBy.append(sort.toString().replace(":", " "));
        return orderBy.toString();
    }
    
    /**
     * 根据自定义原生SQL语句查询,包含拼接searchable条件，自定义条件，自动转换pojo，分页，排序，数据权限，
     * 		求和统计
     * @param inptSql 输入的SQL语句
     * @param searchable 查询条件
     * @param retClass 返回对象的class
     * @param customCondition 自定义的条件
     * @return
     */
    public <M> Page<M> findAllByCustomSQL(String inptSql, Searchable searchable, 
    		Class<M> retClass, Map<String, Object> customCondition){
    	return this.findAllByCustomSQL(inptSql, searchable, retClass, customCondition, "", false);
    }
    
    /**
     * 根据自定义原生SQL语句查询,包含拼接searchable条件，自定义条件，自动转换pojo，分页，排序，数据权限，
     * 		求和统计，支持group by 
     * @param inptSql 输入的SQL语句
     * @param searchable 查询条件
     * @param retClass 返回对象的class
     * @param customCondition 自定义的条件
     * @param groupBySql groupBy的SQL部分
     * @return
     */
    public <M> Page<M> findAllByCustomSQL(String inptSql, Searchable searchable, 
    		Class<M> retClass, Map<String, Object> customCondition, String groupBySql){
    	return this.findAllByCustomSQL(inptSql, searchable, retClass, customCondition, groupBySql, false);
    }
    
    /**
     * 根据自定义原生SQL语句查询,包含拼接searchable条件，自定义条件，自动转换pojo，分页，排序，数据权限;
     * 		求和统计，查询缓存启用;
     * @param inptSql 输入的SQL语句
     * @param searchable 查询条件
     * @param retClass 返回对象的class
     * @param customCondition 自定义的条件
     * @param enableQueryCache 是否启用查询缓存
     * @return
     */
	public <M> Page<M> findAllByCustomSQL(String inptSql, Searchable searchable, 
    		Class<M> retClass, Map<String, Object> customCondition, boolean enableQueryCache){
		return this.findAllByCustomSQL(inptSql, searchable, retClass, customCondition, "", enableQueryCache);
	}
    /**
     * 根据自定义原生SQL语句查询,包含拼接searchable条件，自定义条件，自动转换pojo，分页，排序，数据权限;
     * 		求和统计，查询缓存启用; 支持group by 
     * @param inptSql 输入的SQL语句
     * @param searchable 查询条件
     * @param retClass 返回对象的class
     * @param customCondition 自定义的条件
     * @param groupBySql groupBy的SQL部分
     * @param enableQueryCache 是否启用查询缓存
     * @return
     */
	public <M> Page<M> findAllByCustomSQL(String inptSql, Searchable searchable, 
    		Class<M> retClass, Map<String, Object> customCondition, String groupBySql, boolean enableQueryCache){
    	//考虑是否为统计查询
    	boolean isSummary = StringUtils.isNotBlank(ThreadLocals.getSumPropertys());
    	if(isSummary){
    		searchable.removePageable();
			searchable.removeSort();
    	}
    	
    	StringBuilder sql = new StringBuilder();
    	sql.append(inptSql);
    	//拼接searchble中的条件
    	int paramIndex = searchCallback.prepareSQL(sql, searchable);
    	//拼接自定义条件
    	if(Collections3.isNotEmpty(customCondition)){
    		for(Map.Entry<String, Object> entry : customCondition.entrySet()){
    			sql.append(" and ").append(entry.getKey()).
    				append(" (:").append(paramPrefix).append(paramIndex++).append(")");
    		}
    	}
    	//拼接group by
        if(StringUtils.isNotEmpty(groupBySql)) {
            sql.append(" ").append(groupBySql);
        }
    	//设置排序
    	searchCallback.prepareSQLOrder(sql, searchable);
    	//拼接数据权限
//    	String executeSQL = doPrivilege(sql.toString());
    	String executeSQL = sql.toString();
    	
    	//考虑是否为统计查询
    	org.hibernate.Query q = null;
    	if(isSummary){
    		String summarySQL = getSummarySQL(executeSQL);
        	q = HibernateUtils.getSession(entityManager).createSQLQuery(summarySQL);
        			//.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);	//统计查询直接转换为MAP对象
    	}else{
    		q = HibernateUtils.getSession(entityManager).createSQLQuery(executeSQL)
    				.setResultTransformer(Transformers.aliasToBean(retClass));	//正常查询转换retClass对象
    	}
    	
    	//设置searchable中的参数
    	paramIndex = searchCallback.setValuesForHibernateQ(q, searchable);
    	//设置自定义参数
    	if(Collections3.isNotEmpty(customCondition)){
    		for(Map.Entry<String, Object> entry : customCondition.entrySet()){
    			q.setParameter(paramPrefix + (paramIndex++), entry.getValue());
    		}
    	}
    	//处理分页 合计无需分页
    	Pageable pageable = searchable.getPage();
    	if (searchable.hasPageable() && !isSummary){
    		q.setFirstResult(pageable.getOffset());
			q.setMaxResults(pageable.getPageSize());
    	}
    	
    	q.setCacheable(enableQueryCache);		//设置查询缓存,针对于同session级的缓存；适用于一个session中，对于同一参数相同SQL需要多次查询的情况
    	List<M> list = q.list();
    	
    	//处理总数
    	long total = list.size();
    	if(searchable.hasPageable() && total>0){
			String countSql = "select count(1) from ( " + executeSQL + ") a";
			org.hibernate.Query countQ = HibernateUtils.getSession(entityManager).createSQLQuery(countSql);
    		paramIndex = searchCallback.setValuesForHibernateQ(countQ, searchable);
			if(Collections3.isNotEmpty(customCondition)){
	    		for(Map.Entry<String, Object> entry : customCondition.entrySet()){
	    			countQ.setParameter(paramPrefix + (paramIndex++), entry.getValue());
	    		}
	    	}
    		List<?> countList = countQ.list();
    		if(Collections3.isNotEmpty(countList)){
    			total = Long.parseLong(countList.get(0).toString());
    		}
    	}
    	
    	List retList = list;
    	//统计查询结果集转换,直接返回Map
		if(isSummary && Collections3.isNotEmpty(list)){
			retList = CommonUtils.getSummaryList(list);
			total = 1;
		}
    	
		return new PageImpl<M>(retList, pageable, total);
    }
    
    private String getSummarySQL(String orgSQL){
    	StringBuilder sumPeropertyPart = new StringBuilder(20);
		if(StringUtils.isNotEmpty(ThreadLocals.getSumPropertys())){
			String[] sumProperties = ThreadLocals.getSumPropertys().split(",");
			for(String s : sumProperties){
				int index = s.indexOf(".");		//去除别名
				if(index == 0){
					sumPeropertyPart.append(",sum(").append(CommonUtils.convertJaveBeanStrToUnderLine(s)).
						append(") ").append(s);
				}else{
					sumPeropertyPart.append(",sum(").append(CommonUtils.convertJaveBeanStrToUnderLine(s.substring(index+1))).
					append(") ").append(s.substring(index+1));
				}
			}
		}
		
		String sumSQL = "select " + sumPeropertyPart.substring(1) + " from (" + orgSQL + ") summary";
		return sumSQL;
    }

    public <T> JpaEntityInformation<T, ?> getMetadata(Class<T> entityClass) {
        return JpaEntityInformationSupport.getEntityInformation(entityClass, entityManager);
    }

    public String getEntityName(Class<?> entityClass) {
        return getMetadata(entityClass).getEntityName();
    }


    private void assertConverted(Searchable searchable) {
        if (!searchable.isConverted()) {
            searchable.convert(this.entityClass);
        }
    }


    public void applyEnableQueryCache(Query query) {
        if (enableQueryCache) {
            query.setHint("org.hibernate.cacheable", true);//开启查询缓存
        }
    }

}
